package conf

import (
	"gitee.com/llakcs/agile-go/dir"
	"gitee.com/llakcs/agile-go/log"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
	"io"
	"os"
	"path/filepath"
	"strings"
)

var (
	ZapMaxSize    = 10   //每个日志文件保存多少M，默认10M
	ZapMaxBackUps = 30   //保留多少个备份，默认不限
	ZapMaxAge     = 7    //保留多少天，默认不限
	ZapCompress   = true //是否压缩
	ZapLocalTime  = true //是否使用localTime

)

type LogConf struct {
	MaxSize    int    `mapstructure:"maxsize"`
	MaxBackUps int    `mapstructure:"maxBackUps"`
	MaxAge     int    `mapstructure:"maxAge"`
	Compress   bool   `mapstructure:"compress"`
	LocalTime  bool   `mapstructure:"localTime"`
	LogPath    string `mapstructure:"logPath"`
	Level      string `mapstructure:"level"`
}

func LoadLogger(conf *LogConf) *log.ZapLogger {
	//初始化日志组件
	var logger *log.ZapLogger
	//zop := log.WithZapMsgKey("com.lee.agile")
	if conf.MaxSize == 0 && conf.MaxBackUps == 0 && conf.MaxAge == 0 {
		//找不到日志配置使用默认
		logger = log.NewZapLogger()
	} else {
		//使用自定义配置
		logger = log.NewZapLogger(log.WithZapLogger(BuildLogger(conf)))
	}
	//设置框架使用的日志组件
	log.SetLogger(logger)
	log.Info("The log component is initialized")
	return logger
}

func BuildLogger(conf *LogConf) *zap.Logger {
	var level log.Level
	switch strings.ToLower(conf.Level) {
	case "debug":
		level = log.LevelDebug
	case "info":
		level = log.LevelInfo
	case "warn":
		level = log.LevelWarn
	case "error":
		level = log.LevelError
	case "fatal":
		level = log.LevelFatal
	default:
		level = log.LevelInfo
	}
	if conf.MaxSize == 0 {
		conf.MaxSize = ZapMaxSize
	}
	if conf.MaxBackUps == 0 {
		conf.MaxBackUps = ZapMaxBackUps
	}
	if conf.MaxAge == 0 {
		conf.MaxAge = ZapMaxAge
	}

	debugLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl == zapcore.DebugLevel
	})

	infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl == zapcore.InfoLevel
	})

	errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl == zapcore.ErrorLevel
	})

	warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl == zapcore.WarnLevel
	})
	fatalLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl == zapcore.FatalLevel
	})
	levelMap := make(map[log.Level]zap.LevelEnablerFunc)
	levelMap[log.LevelDebug] = debugLevel
	levelMap[log.LevelInfo] = infoLevel
	levelMap[log.LevelError] = errorLevel
	levelMap[log.LevelWarn] = warnLevel
	levelMap[log.LevelFatal] = fatalLevel

	cores := make([]zapcore.Core, 0)
	var basePath string
	var err error
	if conf.LogPath == "" {
		basePath, err = dir.GetDefaultLogPath() //TODO 这里需要测试
		if err != nil {
			panic("##获取系统默认日志目录失败")
			return nil
		}
	} else {
		basePath = conf.LogPath
	}

	for k, v := range levelMap {
		if level <= k {
			levelPath := BuildLogFilePath(basePath, k.String())
			w := BuildZapWriter(levelPath, conf.MaxSize, conf.MaxBackUps, conf.MaxAge, conf.Compress, conf.LocalTime)
			//两路输出，一个是命令行，一个是文件，按需保留
			core := newCore(v, log.DefaultZapEncoder(), zapcore.AddSync(os.Stdout), zapcore.AddSync(w))
			cores = append(cores, core)
		}
	}

	core := zapcore.NewTee(cores...)
	// 开启开发模式，堆栈跟踪
	caller := zap.AddCaller()
	// 开启文件及行号
	development := zap.Development()
	return zap.New(core, caller, development, zap.AddCallerSkip(2))
}

// 根据存储路径和日志级别获取完整的日志文件路径
func BuildLogFilePath(storagePath string, levelName string) string {
	// 去掉存储路径最后一个字符（斜杆或反斜杠）
	storagePath = filepath.Clean(storagePath)
	logName := levelName + ".log"
	prefix := levelName
	return filepath.Join(storagePath, prefix, logName)
}

func BuildZapWriter(path string, maxsize, maxBackups, maxAge int, compress, localTime bool) io.Writer {

	// 日志分割
	hook := lumberjack.Logger{
		Filename:   path,       // 日志文件路径，默认 os.TempDir()
		MaxSize:    maxsize,    // 每个日志文件保存10M，默认 100M
		MaxBackups: maxBackups, // 保留30个备份，默认不限
		MaxAge:     maxAge,     // 保留7天，默认不限
		Compress:   compress,   // 是否压缩，默认不压缩
		LocalTime:  localTime,
	}
	return zapcore.AddSync(&hook)
}

func newCore(level zap.LevelEnablerFunc, encoder zapcore.Encoder, ws ...zapcore.WriteSyncer) zapcore.Core {
	//设置输出
	w := zapcore.NewMultiWriteSyncer(ws...)
	core := zapcore.NewCore(
		encoder,
		w,
		level,
	)
	return core
}
